﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text;
using Boggle.TheGame;

namespace Boggle {

    /// <summary>
    /// Class to represent each game.
    /// <BR>
    /// Author: David I. Schwartz (dis@mail.rit.edu)
    /// Edited By : Zachary Behrmann
    ///             Robert Husfeldt
    /// <BR>
    /// </summary>
    public class Game {

        #region fields

        private BoggleWindow mainWindow;        // link to main window
        private Dictionary<string, List<string>> dictionary; // Dictionary of all legal words, sorted into lists by keys of up to 4 of their first letters.
        private Stack<string> chronoSolution;   // collection of solved words in chronological order
        private List<string> sortedSolution;    // sorted collection of solved words
        private int wordCount;                  // # of words in dictionary
        private List<Die> dice;                 // list of Die objects
        private int ROWS = BoggleWindow.ROWS;   // number of rows on board
        private int COLS = BoggleWindow.COLS;   // number of columns on board
        private int score;                      // current score
        private int solvedCount;                // current number of solved words

        #endregion fields

        #region properties

        /// <summary>
        /// Needed this property so I could pass it up to BoggleWindow for the AllWord window's creation.
        /// </summary>
        public List<string> SortedSolution
        {
            get { return sortedSolution; }
        }

        #endregion


        #region constructors

        /// <summary>
        /// Create instance of Game.
        /// </summary>
        /// <param name="bw">main window</param>
        public Game(BoggleWindow bw) {
            mainWindow = bw;               // link to main window to access GUI
            processWords(bw.WordFileName); // process words (get count, store words)
            processDice(bw.DiceFileName);  // process dice file and create List of Die objects
            makeBoard();                   // make and assign board, supplied with dice
            reset();                       // reset score, counts, current word, current solved words
        } // constructor Game

        public Game(BoggleWindow bw, Stack<string> chrono, List<string> sorted, int score, int solvedCount) {
            mainWindow = bw;               // link to main window to access GUI
            processWords(bw.WordFileName); // process words (get count, store words)
            chronoSolution = chrono;
            sortedSolution = sorted;
            this.score = score;
            this.solvedCount = solvedCount;
        } // constructor Game

        #endregion constructors

        #region file methods

        /// <summary>
        /// Open words list file, store words, and count:
        /// </summary>
        /// <param name="wordFileName">Name of word list file.</param>
        public void processWords(string wordFileName) {

            try {
                using (TextReader reader = File.OpenText(wordFileName)) {
                    // parse first item (word count);
                    // should also check if parses, > 0 and < int.MaxValue!
                    wordCount = int.Parse(reader.ReadLine());
                    dictionary = new Dictionary<string, List<string>>();

                    // read in words from file:
                    int count = 0;  string currentKey = null; string word = ""; List<string> wordsForKey = new List<string>();
                    while(count < wordCount)
                    {
                       
                        if (currentKey != null)
                        {
                            string newKey = getKey(word);
                                dictionary.Add(currentKey, wordsForKey);
                                currentKey = newKey;
                                wordsForKey = new List<string>();
                        }
                        else
                        {
                            wordsForKey = new List<string>();
                            word = reader.ReadLine().ToUpper();
                            count++;
                            currentKey = getKey(word);
                        }

                        while (getKey(word).Equals(currentKey))
                        {
                            wordsForKey.Add(word);
                            

                            if (reader.Peek() == -1)
                            { break; }
                            word = reader.ReadLine().ToUpper();
                            count++;
                        }

                        if (reader.Peek() == -1)
                        {
                            if (!dictionary.ContainsKey(currentKey))
                            {
                                dictionary.Add(currentKey, wordsForKey);
                            }
                        }

                    }

                    Console.Write("");

                    }
                }
            catch (Exception) {
                MessageBox.Show("Something wrong with reading words file. See if one's stored in DefaultData directory.");
            }

        } // method processWords


        /// <summary>
        /// Method that will get a key for a given word for looking in or creating the dictionary.
        /// Keys are the first few letters of a word, up to four, but less is acceptable for those words with
        /// fewer than four total letters.
        /// </summary>
        /// <param name="word">Word you want the key for.</param>
        /// <returns>A key for the dictionary to use.</returns>
        private string getKey(string word)
        {
            //Simply splits the string into a char[] and then uses as many elements as it can (up to 4).
            //Not complicated, but better to have in a submethod to be used by both checking the dictionary and forming it.
            char[] splitWord = word.ToCharArray();

            if (splitWord.Length >= 4)
            {
                return (splitWord[0].ToString() + splitWord[1].ToString() + splitWord[2].ToString() + splitWord[3].ToString());
            }
            else if (splitWord.Length == 3)
            {
                return (splitWord[0].ToString() + splitWord[1].ToString() + splitWord[2].ToString());
            }
            else if (splitWord.Length == 2)
            {
                return (splitWord[0].ToString() + splitWord[1].ToString());
            }
            else
            {
                return (splitWord[0].ToString());
            }
        }

        /// <summary>
        /// Open dice file and create each Die.
        /// </summary>
        /// <param name="diceFileName">filename of dice file</param>
        public void processDice(string diceFileName) {
            
            dice = new List<Die>();

            try {
                using (TextReader reader = File.OpenText(diceFileName)) {
                    while (reader.Peek() > -1) {

                        // get next string of Die sides (letters):
                        string letters = reader.ReadLine();

                        // add each die to list of dice
                        // each Die needs the sides (string) and # of sides:
                        dice.Add(new Die(letters, letters.Length));
                    }
                }
            }
             catch (Exception) {
                MessageBox.Show("Something wrong with reading dice file. See if one's stored in DefaultData directory.");
            }

        } // method getDice

        #endregion file methods

        #region board method

        /// <summary>
        /// Make the board give List of dice
        /// </summary>
        private void makeBoard() {

            // rand num generator for picking dice:
            Random rand = new Random(); 

            // randomly select a Die, roll it, and then place on board:
            for (int row = 0; row < ROWS; row++) {
                for (int col = 0; col < COLS; col++) {
                    int position = rand.Next(0, dice.Count);       // pick random position in list of dice
                    Die currentDie = dice[position];               // find that die
                    dice.Remove(currentDie);                       // remove from list
                    currentDie.roll();                             // roll that die
                    Button tmpBtn = mainWindow.GameBoard[row, col]; // current board DieButton
                    tmpBtn.Text = currentDie.CurrentSide;        // set DieButton side on board
                }
            }

        } // method makeBoard

        #endregion board method

        #region reset

        /// <summary>
        /// Reset game object values to prepare for new game.
        /// </summary>
        public void reset() {
                       
            chronoSolution = new Stack<string>(0);
            sortedSolution = new List<string>(0);
            score = 0;
            solvedCount = 0;
        
        } // method reset

        #endregion reset

        #region word checks

        /// <summary>
        /// Check if word is already guessed.
        /// </summary>
        /// <param name="word">word to check</param>
        /// <returns>whether or not word already in solution</returns>
        public bool checkSolution(string word) {
            return sortedSolution.Contains(word);
        } // method checkSolution

        /// <summary>
        /// Check whether or not dictionary (words) contains current user-guessed word.
        /// </summary>
        /// <param name="word">word that player guessed</param>
        /// <returns>true if found, otherwise false</returns>
        public bool checkDictionary(string word) {
            
                if(dictionary.ContainsKey(getKey(word.ToUpper()))) //check if the key exists, if it doesn't then the word can't be in there.
                {
                    if (dictionary[getKey(word.ToUpper())].Contains(word.ToUpper())) //Check if an existing key has that word in its value.
                    {
                        return true;
                    }
                }
            return false;
        } // method checkDictionary

        /// <summary>
        /// Add word to list of solved words (both chronological and sorted);
        /// checking for repeats and legality happened in checkSolution and checkDictionary.
        /// </summary>
        /// <param name="word">solved word to store</param>
        public void addWord(string word) {
            chronoSolution.Push(word);
            sortedSolution.Add(word);
            //sortedSolution.Sort(); // rather expensive...can you improve?
            TheGame.MergeySort.MergeSort(sortedSolution);
        } // method addWord

        /// <summary>
        /// Return a string containing all of the sorted, solved words (split by new lines).
        /// </summary>
        /// <returns>string of all sorted words</returns>
        public string[] getSortedWords() {
            return sortedSolution.ToArray();
        } // method getSortedWords

        #endregion word checks

        #region scoring

        /// <summary>
        /// Increase score using simplified rules: 1 point per letter per word, 1-16 letter words permissible.
        /// </summary>
        /// <param name="word">current solved word</param>
        public void increaseScores(string word) {
            score += word.Length;
        } // method increaseScores

        /// <summary>
        /// Increment word count for each solved word.
        /// </summary>
        public void increaseSolvedCount() {
            solvedCount++;
        } // method increaseSolvedCount

        /// <summary>
        /// Return current game score.
        /// </summary>
        public int Score {
            get { return score; }
        }

        /// <summary>
        /// Return current count of solved words.
        /// </summary>
        public int SolvedCount {
            get { return solvedCount; }
        }

        #endregion scoring

        #region solver


        /// <summary>
        ///Using a DFS search method, this will return all the legitimate words in a matrix of Die in a game of Boggle.
        ///Further comenting in code.
        /// </summary>
        /// <param name="matrix">Board's button matrix.</param>
        /// <returns>List of all legitimate strings that can be made on the board.</returns>
        public List<string> getAllWords(Button[,] matrix)
        {
            //create the list we'll be adding to
            List<string> allWords = new List<string>();


            //The following nested for loop will start the DFS fresh on each of the tiles, using all 16
            //as a source.
            for (int Row = 0; Row < ROWS; Row++)
            {
                for ( int Column = 0; Column < COLS; Column++)
                {

                    //Stack needs to be reset every new 'source'. Just in case.
                    Stack<PathNode> toDo = new Stack<PathNode>();

                    //Start each 'path' at a new tile, indicated by for loop's row/column.
                    PathNode startingPathNode = new PathNode(null, matrix[Row, Column].Text.ToUpper(), Row, Column);


                    toDo.Push(startingPathNode); //Push the source of the path on the toDo stack.

                    while (toDo.Count > 0) //Until it runs out of paths to follow, keep going.
                    {

                        //Pop the top, get its row and column, and add it to the list if it isn't already there and is a word
                        //according to the dictionary.
                        PathNode currentNode = toDo.Pop();
                        int tmpRow = currentNode.Row;
                        int tmpColumn = currentNode.Column;

                        if (checkDictionary(currentNode.Content))
                        {
                            if (!allWords.Contains(currentNode.Content))
                            {
                                allWords.Add(currentNode.Content);
                            }
                        }

                        //Look one away in every direction(including diagonols) for potential PathNodes. Only
                        //explained once because they're all the same, except checking a different one (four cardinal adjacencies
                        //and every diagonal one.
                        #region CheckAdjacentTiles
                        if (tmpRow + 1 < ROWS)
                        {
                            //Create the node, using the current node as its parent, and the shifted row and/or column as its row/column.
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow + 1, tmpColumn].Text.ToUpper(), tmpRow + 1, tmpColumn);

                            //If the potential node is 'above' the currentNode (that is, one of the currentNode's parents is the tile that made the
                            //potential node) then the potential node shouldn't be added, can't use tiles twice in one path.
                            if (!currentNode.IsBelow(potential))
                            {
                                //Because of how the Dictionary works, if the depth is at or above 4, you know if any further letters are even capable
                                //of being made into words. If the dictionary does not have a key for current content, no further content will make anything useful.
                                //This path can be ignored.
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else //Otherwise, we throw it on the pile and follow the tiles that lead off it.
                                {
                                    toDo.Push(potential);
                                }
                            }

                        }

                        if (tmpRow - 1 >= 0)
                        {
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow - 1, tmpColumn].Text.ToUpper(), tmpRow - 1, tmpColumn);

                            if (!currentNode.IsBelow(potential))
                            {
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else
                                {
                                    toDo.Push(potential);
                                }
                            }

                        }

                        if (tmpColumn + 1 < COLS)
                        {
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow, tmpColumn + 1].Text.ToUpper(), tmpRow, tmpColumn + 1);
                           
                            if (!currentNode.IsBelow(potential))
                            {
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else
                                {
                                    toDo.Push(potential);
                                }
                            }
                        }

                        if (tmpColumn - 1 >= 0)
                        {
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow, tmpColumn - 1].Text.ToUpper(), tmpRow, tmpColumn - 1);

                            if (!currentNode.IsBelow(potential))
                            {
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else
                                {
                                    toDo.Push(potential);
                                }
                            }
                        }

                        if (tmpColumn - 1 >= 0 && tmpRow + 1 < ROWS)
                        {
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow + 1, tmpColumn - 1].Text.ToUpper(), tmpRow + 1, tmpColumn - 1);

                            if (!currentNode.IsBelow(potential))
                            {
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else
                                {
                                    toDo.Push(potential);
                                }
                            }
                        }

                        if (tmpColumn - 1 >= 0 && tmpRow - 1 >= 0)
                        {
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow - 1, tmpColumn - 1].Text.ToUpper(), tmpRow - 1, tmpColumn - 1);

                            if (!currentNode.IsBelow(potential))
                            {
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else
                                {
                                    toDo.Push(potential);
                                }
                            }
                        }

                        if (tmpColumn + 1 < COLS && tmpRow + 1 < ROWS)
                        {
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow + 1, tmpColumn + 1].Text.ToUpper(), tmpRow + 1, tmpColumn + 1);

                            if (!currentNode.IsBelow(potential))
                            {
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else
                                {
                                    toDo.Push(potential);
                                }
                            }
                        }

                        if (tmpColumn + 1 < COLS && tmpRow - 1 >= 0)
                        {
                            PathNode potential = new PathNode(currentNode, matrix[tmpRow - 1, tmpColumn + 1].Text.ToUpper(), tmpRow - 1, tmpColumn + 1);

                            if (!currentNode.IsBelow(potential))
                            {
                                if (potential.Depth >= 4)
                                {
                                    if (dictionary.ContainsKey(getKey(potential.Content)))
                                    {
                                        toDo.Push(potential);
                                    }
                                }
                                else
                                {
                                    toDo.Push(potential);
                                }
                            }
                        }
                        #endregion
                    }//While(toDo > 0)


                } //Column part of for loop
            } //Row part of for loop

            MergeySort.MergeSort(allWords);

            return allWords;
        }

        #endregion

    } // class Game

} // namespace GSD_HW3
